package testApplets;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Vector;

import marchingCubes.CAADMCube;

import processing.core.PApplet;
import simpleGeom.DepthAnalyser3D;
import simpleGeom.MSegment;
import simpleGeom.Vec2D;
import simpleGeom.Vec3D;

public class VoronoiApplet extends PApplet{
	



		int nX =200;
		int nY = 100;
		int nZ = 20;


		//200x100x50

		// precalculated values
		int nYZ=nY*nZ;
		int nXYZ=nY*nZ*nX;

		// declaration and construction of the voxelgrid
		float[] voxels = new float[nXYZ];
		int[] closestLine=new int[nXYZ];
		float iso=0.5f;
		MSegment[] segments;
		
		CAADMCube mCube=new CAADMCube();
		boolean doExport;
		 ArrayList<MSegment> segmentList;
		public void setup() {
		  size(1200, 700, OPENGL);
		  initSegments4();
		 mCube.marchingCubesMinim(nX,nY,nZ,voxels,iso,1,0,0,0,null);
		}
		public ArrayList<MSegment> placeSegments(float x, float y,float z, int nSegments, float offset,float l){
			ArrayList<MSegment> segmentList=new ArrayList<MSegment>();
			for (int i=0;i<nSegments;i++){
				Vec3D p1=Vec3D.getPolar3D(random(PI), random(PI), offset);
				p1.x+=x;
				p1.y+=y;
				p1.z+=z;
				segmentList.add(addSegment(p1.x,p1.y,p1.z,l,random(PI), random(PI)));
			}
			return segmentList;
		}
		public MSegment addSegment(float x,float y,float z,float l,float a1,float a2){
			
			x=max(x,0);
			y=max(y,0);
			z=max(z,0);
			x=min(x,nX);
			y=min(y,nY);
			z=min(z,nZ);
			Vec3D p1=new Vec3D(x,y,z);
			Vec3D d=Vec3D.getPolar3D(0,a2, l);
			Vec3D p2=new Vec3D();
			p1.x=p1.x-d.x;
			p1.y=p1.y-d.y;
			p1.z=p1.z-d.z;
			p2.x=p1.x+2*d.x;
			p2.y=p1.y+2*d.y;
			p2.z=p1.z+2*d.z;
			return new MSegment(p1.x,p1.y,p1.z,p2.x,p2.y,p2.z);
		}
		public void initSegments4(){
			ArrayList<MSegment> segmentList=new ArrayList<MSegment>();
			MSegment segment=new MSegment(0,0,0,0,100,0);
			segmentList.add(segment);
			segment=new MSegment(150,0,0,150,100,0);
			segmentList.add(segment);
			 
			for (int j=0;j<6;j++){
				for (int i=0;i<segmentList.size();i++){
					MSegment s=segmentList.get(i);
					if (s.id==j){
						segmentList.addAll(replace(s));
					}
				}
			}
			println(segmentList.size());
			
			
			 
			 
			 
				 segment=new MSegment(0,10,nZ,0,90,nZ);
				
				 ArrayList<MSegment> segmentList2=new ArrayList<MSegment>();
				segment=new MSegment(120,10,nZ,120,90,nZ);
				
				segmentList2.add(segment);
				 
				for (int j=0;j<6;j++){
					for (int i=0;i<segmentList2.size();i++){
						MSegment s=segmentList2.get(i);
						if (s.id==j){
							segmentList2.addAll(replace(s));
						}
					}
				}
				println(segmentList.size());
				segmentList.addAll(segmentList2);
				scaleToBounds(segmentList);
				
				segments=new MSegment[segmentList.size()];
				 segmentList.toArray(segments);
				 for (int i=0;i<segmentList.size();i++){
					MSegment s=segmentList.get(i);
					s.setZ1(getZ(s.x1(),s.y1()));
					s.setZ2(getZ(s.x2(),s.y2()));
					s.setZ2(s.z1());
				 }
				
						
			 doCalculate();
		}
		public void scaleToBounds(ArrayList<MSegment>segmentList){
			float[] bds=getBounds(segmentList);
			float sX=nX*1f/(bds[3]-bds[0]);
			float sY=nY*1f/(bds[4]-bds[1]);
			float sZ=nZ*1f/(bds[5]-bds[2]);
			
			float tX=-bds[0];
			float tY=-bds[1];
			float tZ=-bds[2];
			for (int i=0;i<bds.length;i++){
				println("i: "+bds[i]);
			}
			println("sX: "+sX);
			for (int i=0;i<segmentList.size();i++){
				MSegment s=segmentList.get(i);
				s.translate(tX,tY,tZ);
				s.scale(sX,sY,sZ);
			}
		}
		public float getZ(float x,float y){
			return random(0,nZ);
			//return sin(4*PI*x/nX)+sin(3.2f*PI*y/nY)*0.2f*nZ+nZ*0.5f;
		}
		public float[] getBounds(ArrayList<MSegment>segments){
			float[]bds=new float[6];
			bds[0]=Float.POSITIVE_INFINITY;
			bds[1]=Float.POSITIVE_INFINITY;
			bds[2]=Float.POSITIVE_INFINITY;
			bds[3]=Float.NEGATIVE_INFINITY;
			bds[4]=Float.NEGATIVE_INFINITY;
			bds[5]=Float.NEGATIVE_INFINITY;
			for (int i=0;i<segments.size();i++){
				MSegment s=segments.get(i);
				bds[0]=min(s.x1(),bds[0]);
				bds[0]=min(s.x2(),bds[0]);
				bds[1]=min(s.y1(),bds[1]);
				bds[1]=min(s.y2(),bds[1]);
				bds[2]=min(s.z1(),bds[2]);
				bds[2]=min(s.z2(),bds[2]);
				
				bds[3]=max(s.x1(),bds[3]);
				bds[3]=max(s.x2(),bds[3]);
				bds[4]=max(s.y1(),bds[4]);
				bds[4]=max(s.y2(),bds[4]);
				bds[5]=max(s.z1(),bds[5]);
				bds[5]=max(s.z2(),bds[5]);
			}
			return bds;
		}
		public  ArrayList<MSegment> replace(MSegment s){
			ArrayList<MSegment> newSegments=new ArrayList<MSegment>();
			float zoffset=nZ*0.01f;
			if (s.id%2==0)zoffset*=-1;
			float sc=0.6f;
			float sc2=0.8f;
			float sc3=0.2f;
			float dA=0.9f;
			
			float dX=s.dX();
			float dY=s.dY();
			float dZ=s.dZ();
			
			float l=s.length();
			float angle=Vec2D.getAngle(dX, dY);
			
			float angle1=angle+dA;
			float angle2=angle-dA;
			
			float pX=(dX*sc) +s.x1();
			float pY=(dY*sc) +s.y1();
			float pZ=(dZ*sc) +s.z1();
			
			Vec2D b1=Vec2D.getPolar(angle1,sc2*l);
			MSegment s1=new MSegment(pX,pY,-.5f*zoffset,pX+b1.x,pY+b1.y,zoffset);
			
			s1.setX1(s1.x1()+s1.dX()*sc3);
			s1.setY1(s1.y1()+s1.dY()*sc3);
			s1.setZ1(s1.z1()+s1.dZ()*sc3);
			s1.id=s.id+1;
			Vec2D b2=Vec2D.getPolar(angle2, sc2*l);
			
			MSegment s2=new MSegment(pX,pY,-zoffset,b2.x+pX,b2.y+pY,zoffset);
			s2.setX1(s2.x1()+s2.dX()*sc3);
			s2.setY1(s2.y1()+s2.dY()*sc3);
			s2.setZ1(s2.z1()+s2.dZ()*sc3);
			s2.id=s.id+1;
			newSegments.add(s1);
			newSegments.add(s2);
			return newSegments;
		}
		
		public void initSegments3(){
			 ArrayList<MSegment> segmentList=new ArrayList<MSegment>();
			 for (int i=0;i<10;i++){
				 segmentList.add(addSegment(random(nX),random(nY),random(nZ),10,random(PI), random(PI)));
			 }
			 ArrayList<MSegment> newSegments=new ArrayList<MSegment>();
			 for (int i=0;i<10;i++){
				 MSegment s=segmentList.get(i);
				 newSegments.addAll( placeSegments(s.cX(),s.cY(),s.cZ(),5,20,10));
				
			 }
			 Collections.shuffle(newSegments);
			 ArrayList<MSegment> newSegments2=new ArrayList<MSegment>();
			 for (int i=0;i<20;i++){
				 MSegment s=newSegments.get(i);
				 newSegments2.addAll( placeSegments(s.cX(),s.cY(),s.cZ(),5,20,5));
				
			 }
			 segmentList.addAll(newSegments);
			 segmentList.addAll(newSegments2);
			 segments=new MSegment[segmentList.size()];
			 segmentList.toArray(segments);
			 doCalculate();
		}
		public void initSegments2(){
			 ArrayList<MSegment> segmentList=new ArrayList<MSegment>();
			 int nSx=7;
			 int nSy=4;
			 float dX=nX*1f/nSy;
			 float dY=nY*1f/nSy;
			 for (int x=0;x<nSx;x++){
				 for (int y=0;y<nSy;y++){
					 float cX1=x*dX;
					  float cX2=cX1+2*random(-dX,dY);
					  float cY1=y*dY;
					  float cY2=cY1+2*random(-dY,dY);
					  float cZ1=0;
					  float cZ2=nZ;
					  if ((y%3==0&&x%2==0)||(y%3!=0)){
						  MSegment s=new MSegment(cX1,cY1,cZ1,cX2,cY2,cZ2);
						  segmentList.add(s);
					  }
				 } 
			 }
			 for (int x=0;x<20;x++){
				  MSegment s=new MSegment(nX*0.6f+random(40),nY*0.5f+random(40),random(7),nX*0.6f+random(40),nY*0.5f+random(20),random(7));
				  segmentList.add(s);
			  }
			 segments=new MSegment[segmentList.size()];
			 segmentList.toArray(segments);
			 doCalculate();
		}
		public void initSegments(){
			
			 ArrayList<MSegment> segmentList=new ArrayList<MSegment>();
			  int xS=400;
			  for (int x=0;x<xS;x++){
				  float cX1=x*1f*nX/xS;
				  float cX2=cX1;
				  if (x%2==0)cX2+=3f*nX/xS;
				  else{
					  cX2-=3f*nX/xS;
				  }
				  float cY1=20*(x%15);
				  float cY2=10*(x%11);
				  float cZ1=nZ*.3f*x%2;
				  float cZ2=nZ*0.5f+nZ*.5f*x%2;
				  MSegment s=new MSegment(cX1,cY1,cZ1,cX2,cY2,cZ2);
				  segmentList.add(s);
			  }
			  xS=60;

			 /* for (int x=0;x<xS;x++){
				  float cX1=x*1f*nX/xS;
				  float cX2=cX1;
				  if (x%2==0)cX2+=3f*nX/xS;
				  else{
					  cX2-=3f*nX/xS;
				  }
				  float cY1=20*(x%10);
				  float cY2=10*(x%8);
				  float cZ1=nZ;
				  float cZ2=nZ;
				  Segment s=new Segment(cX1,cY1,cZ1,cX2,cY2,cZ2);
				  segmentList.add(s);
			  }*/
			 

			  for (int x=0;x<xS;x++){
				  float cX=x*1f*nX/xS;
				  float cY1=15*(x%11);
				  float cY2=10*(x%7);
				  float cZ1=0;
				  float cZ2=nZ;
				  MSegment s=new MSegment(cX,cY1,cZ1,cX,cY2,cZ2);
				  segmentList.add(s);
			  }
			  
			  for (int x=0;x<20;x++){
				  MSegment s=new MSegment(nX*0.6f+random(40),nY*0.5f+random(40),random(7),nX*0.6f+random(40),nY*0.5f+random(40),random(7));
				  segmentList.add(s);
			  }
			  segments=new MSegment[segmentList.size()];
			  segmentList.toArray(segments);
			  doCalculate();
		}
		public void addRandom(float x,float y,float z){
			
		}
		public void calculateDistances(){
		   for (int x=0;x<nX;x++) {
			   println("x: "+x+"/"+nX);
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		        int i=getIndex(x, y, z);
		        for (int j=0;j<segments.length;j++){
		         MSegment seg=segments[j];
		         float d= seg.distToSegSqr(x,y,z);
		          if (j==0||voxels[i]>d){
		            voxels[i]=d;
		            closestLine[i]=(int)(j%(segments.length*0.5f));
		          }
		        }
		       
		      }
		    }
		  }
		}
		DepthAnalyser3D depth3D;
		public void calculateVoronoi(){
			
		   for (int x=0;x<nX;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		        int i=getIndex(x, y, z);
		        voxels[i]=0;
		        int x1=max(0,x-1);
		        int y1=max(0,y-1);
		        int z1=max(0,z-1);
		        int x2=min(x+1,nX-1);
		        int y2=min(y+1,nY-1);
		        int z2=min(z+1,nZ-1);
		        
		        int iDiff=0;
		        for (int cX=x1;cX<x2;cX++){
		        	for (int cY=y1;cY<y2;cY++){
		        		for (int cZ=z1;cZ<z2;cZ++){
				        	int iNb=this.getIndex(cX, cY, cZ);
				        	if (closestLine[i]!=closestLine[iNb]) {
				        		iDiff++;
				        	}
				        }
			        }
		        }
		       /* if (z>0&&closestLine[i]!=closestLine[i-1]) 
		        	iDiff++;
		        if (z<nZ-1&&closestLine[i]!=closestLine[i+1]) 
		        	iDiff++;
		        if (y<nY-1&&closestLine[i]!=closestLine[i+nZ]) 
		        	iDiff++;
		        if (y>0&&closestLine[i]!=closestLine[i-nZ]) 
		        	iDiff++;
		        if (x<nX-1&&closestLine[i]!=closestLine[i+nYZ]) 
		        	iDiff++;
		        if (x>0&&closestLine[i]!=closestLine[i-nYZ]) 
		        	iDiff++;*/
		        if (iDiff>=1){
		        	voxels[i]=0;
		        }
		        else{
		        	
		        	voxels[i]=10f;
		        }
		        if (x==0||x==nX-1||y==0||y==nY-1||z==0||z==nZ-1){
		        	//voxels[i]=1;
		        }
		      }
		    }
		  }
		  /* for (int x=0;x<nX;x++) {
			    for (int y=0;y<nY;y++) {
			      for (int z=0;z<nZ;z++) {
			    	  int i=getIndex(x, y, z);
			    	  int cLine=closestLine[i];
			    	  Segment s=segments[cLine];
			    	  float dZ=abs(z-nZ*0.5f);
			    	  if (s.id>2&&(dZ/(nZ*0.5f))>(8-s.id)/8f){
			    		  voxels[i]=0;
			    	  }
			      }
			    }
			  }*/
		 
		   depth3D=new DepthAnalyser3D();
		   depth3D.calculateDepth(voxels, nX, nY, nZ);
		   voxels=depth3D.depthSingle;
		   normalize(voxels);
		   for (int i=0;i<voxels.length;i++){
			   voxels[i]=1- voxels[i];
		   }
		  
		}
		
		public void cropOutterShape(){
			for (int x=0;x<nX;x++) {
			    for (int y=0;y<nY;y++) {
			      for (int z=0;z<nZ;z++) {
			    	  int i=getIndex(x, y, z);
			    	 float value=(sin(5f*x*PI/nX  + 1.4f)+1)*0.2f+0.5f;
			    	  value+=(sin(5f*y*PI/nY  + 1.4f)+1)*0.2f+0.5f;
			    	 float d=value*0.5f-z*1f/nZ;
			    	 if (d<0){
			    	  voxels[i]=0;
			    	 }
			      }
			  }
		  }
		}

		public void calculateBorders(int value){
		   for (int x=0;x<nX;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		        if (x==0||x==nX-1||y==0||y==nY-1||z==0||z==nZ-1){
		          int i=getIndex(x, y, z);
		          voxels[i]=value;
		        }
		      }
		    }
		  }
		}

		public void mirror(){
		  for (int x=0;x<nX*0.5;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		         int i1=getIndex(x,y,z);
		         int i2=getIndex(nX-x-1,y,z);
		         voxels[i1]=voxels[i2];
		      }
		    }
		  }
		}

		public void doCalculate() {
		 
		 
		  // looping to draw the voxels
		 
		  calculateDistances();
		  calculateVoronoi();
		  calculateBorders(0);
		  //cropOutterShape();
		  //mirror();
		  //normalize(voxels);
		  
		  
		}
		float rot=0;
		public void draw() {
		
		 mCube.marchingCubesMinim(nX,nY,nZ,voxels,iso,1,0,0,0,null);
		 if (doExport) {
		    doExport = false;
		    mCube.saveObj("symmetric"+this.minute()+".obj");
		  }
		  
		  // setup of scene
		  background(0);
		  camera(0, 0,200, 0, 0, 0, 0, 1, 0);
		  lights();
		  //pointLight(200, 210, 200, 0, 0, 0);
		  noStroke();
		  rotateY(rot);
		  translate(-nX*0.5f, -nY*0.5f, -nZ*0.5f);
		  iso=mouseX*1f/width;
		  iso=max(0.02f,iso);
		  iso=min(0.98f,iso);
		  println(iso);
		  
		 beginShape(TRIANGLES);
		  if (mCube.triangles.length>0){
		    for (int i=0;i<mCube.triangles.length;i++){
		      int vI=mCube.triangles[i];
		      vertex(mCube.xc[vI],mCube.yc[vI],mCube.zc[vI]);
		    }
		  }
		 
		  endShape();
		  stroke(255,0,0);
		  drawSegments();
		 
		  //drawAsQuads();
		}

		public void drawSegments(){
			stroke(255,0,0);
			for (int i=0;i<segments.length;i++){
				MSegment s=segments[i];
				line(s.x1(),s.y1(),s.z1(),s.x2(),s.y2(),s.z2());
			}
			stroke(0,255,0);
			
		}
		public void normalize(float[] values) {
		  float maxV = Float.NEGATIVE_INFINITY;
		  float minV = Float.POSITIVE_INFINITY;
		  for (int i = 0; i < values.length; i++) {
		    float v = values[i];
		    maxV = max(maxV, v);
		    minV = min(minV, v);
		  }
		  float dX = maxV - minV;
		  for (int i = 0; i < values.length; i++) {
		    float v = values[i];
		    values[i] = (v - minV) / dX;
		  }
		  println("min: "+minV);
		  println("max: "+maxV);
		}

		// gui
		public void keyPressed() {
		  if (key == 'e')
		    doExport = true;
		  if (key == 'r')
			    rot+= PI*0.5f;
		}
		
		public void drawAsQuads(){
			  // looping to draw the voxels
			  beginShape(QUADS);
			  for (int x=0;x<nX-1;x++) {
			    for (int y=0;y<nY-1;y++) {
			      for (int z=0;z<nZ-1;z++) {
			        int i=getIndex(x, y, z);
			        //  draw a face between voxel and z neighbour
			        if (voxels[i]<iso!=voxels[i+1]<iso) {
			          drawZQuad(x, y, z+0.5f);
			        }
			        //  draw a face between voxel and y neighbour
			        if (voxels[i]<iso!=voxels[i+nZ]<iso) {
			          drawYQuad(x, y+0.5f, z);
			        }
			        //  draw a face between voxel and x neighbour
			        if (voxels[i]<iso!=voxels[i+nYZ]<iso) {
			          float xM=x+0.5f;
			          drawXQuad(x+0.5f, y, z);
			        }
			      }
			    }
			  }
			  endShape();
			}

			void drawZQuad(float x,float y,float z){
			  vertex(x-0.5f,y-0.5f,z);
			  vertex(x-0.5f,y+0.5f,z);
			  vertex(x+0.5f,y+0.5f,z);
			  vertex(x+0.5f,y-0.5f,z);
			}
			void drawYQuad(float x,float y,float z){
			  vertex(x-0.5f,y,z-0.5f);
			  vertex(x-0.5f,y,z+0.5f);
			  vertex(x+0.5f,y,z+0.5f);
			  vertex(x+0.5f,y,z-0.5f);
			}
			void drawXQuad(float x,float y,float z){
			  vertex(x,y-0.5f,z-0.5f);
			  vertex(x,y-0.5f,z+0.5f);
			  vertex(x,y+0.5f,z+0.5f);
			  vertex(x,y+0.5f,z-0.5f);
			}
			public int getIndex(int x, int y, int z) {
				  return x*nY*nZ+y*nZ+z;
				}
				public int getX(int i) {
				  return i/(nY*nZ);
				}
				public int getY(int i) {
				  return (i/nZ)%nY;
				}
				public int getZ(int i) {
				  return i%nZ;
				}
	}


